# coding: utf-8
import sys, os
sys.path.append(os.pardir)
import pickle
import numpy as np
from collections import OrderedDict
from common.layers import *
from common.gradient import numerical_gradient

class SimpleConvNet:
    """简单的ConvNet
    哦这里应该是还没有涉及到批量处理，因为设置了输入的维度为：通道数，高，长
    而批量处理时，输入的维度应该为：数量，通道数，高，长

    conv - relu - pool - affine - relu - affine - softmax

    Parameters
    ---------
    input_size ：输入大小 (MNIST的情况下为784）
    hidden_size_list : 隐藏层的神经元数量的列表（e.g. [100, 100, 100])
    output_size : 输出大小（MNIST的情况下为10）
    activation： ’relu‘ or ’sigmoid‘
    weight_init_std : 指定权重的标准差（e.g. 0.01)
          指定’relu‘或’he'的情况下设定"He的初始值“
          指定‘sigmoid'或’xavier‘的情况下设定”Xavier的初始值“
    """
    def __init__(self, input_dim=(1, 28, 28),
                 conv_param={'filter_num':30, 'filter_size':5, 'pad':0, 'stride':1},
                 hidden_size=100, output_size=10, weight_init_std=0.01):
        filter_num = conv_param['filter_num']  # 滤波器的数量，滤波器的数量本来不是应该和输入数据的数量对应吗？一个输入对应一个滤波器
        filter_size = conv_param['filter_size']  # 滤波器的大小？？？滤波器的大小不应该是有高有长吗？我很疑惑？所以滤波器一般都是正方形大小是吧
        filter_pad = conv_param['pad']  # 填充？这里稍微疑惑的地方是，位置，之前填充不是一直放在最后的吗？
        filter_stride = conv_param['stride']  # 步幅
        input_size = input_dim[1]  # 我直接超级疑惑？倒是也可以稍微理解，就是同时输入28个二维数组，倒也还是单条数据处理的形式，那么就是，输入层的神经元数量为28，每个神经元中放入一个28的一维数组？
        # 啊，这个卷积的形式怎么那么奇怪，之前不一直讲的都是三维式的输入，怎么现在是二维式的输入了？往下走走先
        conv_output_size = (input_size - filter_size + 2*filter_pad) / filter_stride + 1  # 这个公式还看得出来，但怎么感觉那么奇怪？这里不应该是描述卷积操作之后的形状吗？怎么叫size？
        pool_output_size = int(filter_num * (conv_output_size/2) * (conv_output_size/2))  # 哇这个池化的操作，。。。我就啥也看不出来了呀。。。

        # 初始化权重
        self.params = {}
        self.params['W1'] = weight_init_std * \
                            np.random.randn(filter_num, input_dim[0], filter_size, filter_size)  # 哦哦，原来你是卷积层的权重啊，我之前还以为谁呢？原来真正代表卷积层的在这啊，之前那些都不算的
        self.params['b1'] = np.zeros(filter_num)  # 这个对的，偏置和滤波器的数量对应
        self.params['W2'] = weight_init_std * \
                            np.random.rand(pool_output_size, hidden_size)  # 这个应该是池化层之后的那个隐藏层的权重，谓之全连接层，所以说，从输出层到池化层之间没有隐藏层了呗，上面的卷积层其实就是一个隐藏层，又或者说，它是之前隐藏层的一种特殊化，好像池化这一层是不需要什么权重的是吗？
        self.params['b2'] = np.zeros(hidden_size)  # 和之前好像没差，几个神经元，几个偏置
        self.params['W3'] = weight_init_std * \
                            np.random.randn(hidden_size, output_size)  # 最后的那个隐藏层，也是全连接层
        self.params['b3'] = np.zeros(output_size)

        # 生成层
        # 哦，每个层里面都是有权重的耶，现在我们生成层，我们也同时要往层里面装满参数
        self.layers = OrderedDict()  # 要有序
        self.layers['Conv1'] = Convolution(self.params['W1'], self.params['b1'],
                                           conv_param['stride'],conv_param['pad'])  # 生成这样的一个卷积层，和全连接层也是差不多的，就是特殊一些，它的权重别全连接的复杂一些，然后多了两个参数，除了权重和偏置之外，还有步幅和填充
        self.layers['Relu1'] = Relu()  # 激活层，和原来一样
        self.layers['Pool1'] = Pooling(pool_h=2, pool_w=2, stride=2)  # 池化层的生成比较简单，没有那么多复杂的参数，所以上面没有用高斯分布来生成
        self.layers['Affine1'] = Affine(self.params['W2'], self.params['b2'])  # 全连接层，和之前一样生成
        self.layers['Relu2'] = Relu()
        self.layers['Affine2'] = Affine(self.params['W3'], self.params['b3'])  # 全连接层生成

        self.last_layer = SoftmaxWithLoss()  # 最后的一层，既要得出最终输出，也要根据这个输出计算损失函数值

    # 神经网络的推理过程
    def predict(self, x):
        # 逐层调用各个层进行运算即可
        for layer in self.layers.values():
            x = layer.forward(x)

        return x

    # 计算损失函数
    def loss(self, x, t):
        y = self.predict(x)
        return self.last_layer.forward(y, t)

    # 计算识别精确度
    def accuracy(self, x, t, batch_size=100):
        if t.ndim != 1 : t = np.argmax(t, axis=1)  # 将hot标签形式转换一下，这里也是取索引，所以和下面的对得上

        acc = 0.0

        # 奇怪的统计逻辑？
        # 怎么？一批一批的取出来统计？
        # 大数据计算时被优化了效率，所以这样一批一批的核对？
        # 100条100条的取数据来核对
        # 哦，那这个逻辑似乎也不是特别奇怪，比较正常
        # 就是我一次取一条来对，我现在优化一下，一次取100条来对（应该是大数据的处理时，有专门针对优化过）
        for i in range(int(x.shape[0] / batch_size)):
            tx = x[i*batch_size: (i+1)*batch_size]
            tt = t[i*batch_size: (i+1)*batch_size]
            y = self.predict(tx)  # 这里的y是已经经过softmax正规化的了
            y = np.argmax(y, axis=1)  # 化成和t一样的形状，返回最大值的索引
            acc += np.sum(y == tt)

        return acc/ x.shape[0]

    # 数值微分求梯度
    def numerical_gradient(self, x, t):
        loss_w = lambda w: self.loss(x, t)  # 简易建立一个函数，把权重和损失函数值比较直接的关联到一起

        grads = {}
        # 越来越懒，越来越聪明
        for idx in (1, 2, 3):
            grads['W' + str(idx)] = numerical_gradient(loss_w, self.params['W' + str(idx)])
            grads['b' + str(idx)] = numerical_gradient(loss_w, self.params['b' + str(idx)])

        return grads
    # 误差反向传播发求解梯度
    def gradient(self, x, t):

        # forward
        self.loss(x, t)

        # backward
        dout = 1
        dout = self.last_layer.backward(dout)

        layers = list(self.layers.values())
        layers.reverse()  # 将层顺序反转
        for layer in layers:
            dout = layer.backward(dout)

        # 从各层里把梯度拿出来
        grads = {}
        grads['W1'], grads['b1'] = self.layers['Conv1'].dW, self.layers['Conv1'].db
        grads['W2'], grads['b2'] = self.layers['Affine1'].dW, self.layers['Affine1'].db
        grads['W3'], grads['b3'] = self.layers['Affine2'].dW, self.layers['Affine2'].db

        return grads

    # 保存训练好的权重参数
    def save_params(self, file_name="params.pkl"):
        pramas = {}
        for key, val in self.params.items():
            pramas[key] = val
        with open(file_name, 'wb') as f:
            pickle.dump(pramas, f)











